import 'dart:convert';
import 'dart:io';

/// https://pub.dev/packages/logger/install

/// [Level]s to control logging output.
/// Logging can ben enable to levels above certain [Level]
enum Level {
  all(0, 'ALL'),
  trace(1000, 'TRACE'),
  debug(2000, 'DUG'),
  info(3000, 'INFO'),
  warning(4000, 'WARN'),
  error(5000, 'ERROR'),
  fatal(6000, 'FATAL'),
  off(10000, 'NONE');

  final int value;
  final String description;

  const Level(this.value, this.description);
}

class LogEvent {
  final Level level;
  final dynamic message;
  final Object? error;
  final StackTrace? stackTrace;

  /// time when this log was created.
  final DateTime time;
  final bool isWeb;

  LogEvent(
      this.level,
      this.message, {
        DateTime? time,
        this.error,
        this.stackTrace,
        required this.isWeb,
      }) : time = time ?? DateTime.now();

  @override
  String toString() {
    List<String> lines = stackTrace.toString().split('\n');
    if (!isWeb) {
      String line = '';
      if (lines.length > 2) {
        line = lines[2].split('(')[1].split(")")[0];
      }
      return "$line ${time.toIso8601String()} [${level.description}] $message";
    } else {
      List<String> items = lines[3].split(" ");
      String line = "${items[0]} ${items[1]}";
      return "$line ${time.toIso8601String()} [${level.description}] $message";
    }
  }
}

abstract class LogOutput {
  Future<void> init() async {}

  Future<void> destroy() async {}

  void output(LogEvent event);
}

/// Writes the log output to a console.
class ConsoleOutput extends LogOutput {
  @override
  void output(LogEvent event) {
      print(event.toString());
  }
}

/// Writes the log output to a file.
class FileOutput extends LogOutput {
  final File file;
  final bool overrideExisting;
  final Encoding encoding;
  IOSink? _sink;

  FileOutput({
    required this.file,
    this.overrideExisting = false,
    this.encoding = utf8,
  });

  @override
  Future<void> init() async {
    _sink = file.openWrite(
      mode: overrideExisting ? FileMode.writeOnly : FileMode.writeOnlyAppend,
      encoding: encoding,
    );
  }

  @override
  void output(LogEvent event) {
    _sink?.write(event.toString());
    _sink?.writeln();
  }

  @override
  Future<void> destroy() async {
    await _sink?.flush();
    await _sink?.close();
  }
}

/// Use instances of logger to send log messages.
class _LoggerImpl {
  static Level level = Level.debug;
  static LogOutput Function() defaultOutput = () => ConsoleOutput();

  final LogOutput _output;
  final Level _level;
  bool _isWeb = false;
  bool get isWeb => _isWeb;

  _LoggerImpl({
    LogOutput? output,
    Level? level,
  })  : _level = level ?? _LoggerImpl.level,
        _output = output ?? defaultOutput() {
    Future.wait([_output.init()]);
    try {
      Platform.isMacOS;
    } catch (e) {
      _isWeb = true;
    }
  }

  void log(
      Level level,
      dynamic message, {
        DateTime? time,
        Object? error,
        StackTrace? stackTrace,
      }) {
    var logEvent = LogEvent(level, message, time: time, error: error, stackTrace: stackTrace ?? StackTrace.current, isWeb: _isWeb);
    if (level.value > _level.value) {
      _output.output(logEvent);
    }
  }

  void trace(dynamic message) {
    log(Level.trace, message);
  }

  void debug(dynamic message) {
    log(Level.debug, message);
  }

  void info(dynamic message) {
    log(Level.info, message);
  }

  void warning(dynamic message) {
    log(Level.warning, message);
  }

  void error(dynamic message) {
    log(Level.error, message);
  }
}

final logger = _LoggerImpl();
